﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace UC.BaseMVVM
{
    public abstract class ModelBase : DomainObject, IModelBase
    {
        protected ValidadorConfig ConfiguracoesDeValidacao;
        private ObservableCollection<string> _todosErros;
        private readonly List<string> _propriedadesDoModel = new List<string>();
        private Type _tipoObjetoModel;
        private DateTime _horaDisparoExecucaoValidacao;
        protected Func<object, PropertyChangedEventArgs, bool> ExecucaoDoDisparoDaValidacao;

        protected ModelBase()
        {
            var tipoObjetoModel = GetType();
            Inicializacao(tipoObjetoModel);
        }

        protected ModelBase(Type tipoObjetoModel, ValidadorConfig configuracoesDeValidacao = default(ValidadorConfig))
        {
            Inicializacao(tipoObjetoModel, configuracoesDeValidacao);
        }

        private void Inicializacao(Type tipoObjetoModel, ValidadorConfig configuracoesDeValidacao = default(ValidadorConfig))
        {
            _tipoObjetoModel = tipoObjetoModel;

            PropertyChanged += OnValidarErros;
            ErrorsChanged += OnErrorsChanged;

            CarregarListaDePropriedadesDoModel();
            AplicarConfiguracoesDeValidacao(configuracoesDeValidacao);
            ValidaTodosAsPropriedades();
        }

        private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
        {
            CarregarTodosOsErrosNoComponenteVisualValidacaoControl();
        }

        private void OnValidarErros(object sender, PropertyChangedEventArgs e)
        {
            var naoValidarQuandoAlteracoesForemEmAllErrors = e.PropertyName == "TodosErros";
            if (naoValidarQuandoAlteracoesForemEmAllErrors)
                return;

            PrepararInicioValidacao();
            DispararValidacao(sender, e);
        }

        private void PrepararInicioValidacao()
        {
            ExecucaoDoDisparoDaValidacao = (objectSender, eventArgs) =>
                {
                    var horarioQueDigitouAlterouOCampo = DateTime.Now;
                    _horaDisparoExecucaoValidacao = horarioQueDigitouAlterouOCampo;

                    Thread.Sleep(ConfiguracoesDeValidacao.TempoDeEsperaParaValidacao);

                    var seHouveNovoDigitoAlteracaoNoCampo = _horaDisparoExecucaoValidacao >
                                                            horarioQueDigitouAlterouOCampo;
                    if (seHouveNovoDigitoAlteracaoNoCampo)
                        return false;

                    var valorDaPropriedade = objectSender.GetType().GetProperty(eventArgs.PropertyName).GetValue(objectSender);
                    ValidateProperty(eventArgs.PropertyName, valorDaPropriedade);

                    return true;
                };
        }

        protected virtual void DispararValidacao(object sender, PropertyChangedEventArgs e)
        {
            ExecucaoDoDisparoDaValidacao.BeginInvoke(sender, e, OnExecucao, ExecucaoDoDisparoDaValidacao);
        }

        protected void OnExecucao(IAsyncResult ar)
        {
            var executouAValidacao = ((Func<object, PropertyChangedEventArgs, bool>)ar.AsyncState).EndInvoke(ar);
            if (!executouAValidacao)
                return;

            //_horaDisparoExecucaoValidacao = default(DateTime);
        }

        private void CarregarListaDePropriedadesDoModel()
        {
            var seAListaJaTiverSidoCarregada = _propriedadesDoModel.Count > 0;
            if (seAListaJaTiverSidoCarregada)
                return;

            if (_tipoObjetoModel == null)
                throw new ApplicationException("Falta passar o tipo do objeto model por meio do método \"CarregarTipoObjetoModel\". ");

            var nomeCompletoDaClasseDoObjetoModel = _tipoObjetoModel.FullName;
            var type =
                Assembly.GetAssembly(_tipoObjetoModel)
                        .GetTypes()
                        .SingleOrDefault(x => x.FullName == nomeCompletoDaClasseDoObjetoModel);
            if (type == null)
                return;

            var informacoesDasPropriedades = type.GetProperties();
            var nomeDoModel = _tipoObjetoModel.Name;
            foreach (var propertyInfo in informacoesDasPropriedades)
            {
                var propriedadeNaoEhDaModel = propertyInfo.DeclaringType != null &&
                                              propertyInfo.DeclaringType.Name != nomeDoModel;
                if (propriedadeNaoEhDaModel)
                    continue;

                var nomeDaPropriedade = propertyInfo.Name.ToString(CultureInfo.InvariantCulture);
                _propriedadesDoModel.Add(nomeDaPropriedade);
            }
        }

        private void AplicarConfiguracoesDeValidacao(ValidadorConfig configuracoesDeValidacao)
        {
            ConfiguracoesDeValidacao = configuracoesDeValidacao == default(ValidadorConfig)
                                           ? new ValidadorConfig()
                                           : configuracoesDeValidacao;
        }

        private void ValidaTodosAsPropriedades()
        {
            if (!ConfiguracoesDeValidacao.InicializarModelAutoValidando)
                return;

            var typeModel = GetType();
            var allErrors = new List<string>();
            foreach (var propriedade in _propriedadesDoModel)
            {
                var valorDaPropriedade = typeModel.GetProperty(propriedade).GetValue(this);
                ValidateProperty(propriedade, valorDaPropriedade);

                var errosDaPropriedade = ErrorsContainer.GetErrors(propriedade);
                var errosEmLista = errosDaPropriedade.Select(x => x.ErrorMessage);

                allErrors.AddRange(errosEmLista);
            }

            TodosErros = new ObservableCollection<string>(allErrors);
        }


        public bool ModelValido
        {
            get
            {
                var ehValido = !HasErrors;
                return ehValido;
            }
        }

        //

        public ObservableCollection<string> TodosErros
        {
            get { return _todosErros; }
            private set
            {
                _todosErros = value;
                RaisePropertyChanged("TodosErros");
            }
        }

        //

        private void CarregarTodosOsErrosNoComponenteVisualValidacaoControl()
        {
            if (!ErrorsContainer.HasErrors)
            {
                TodosErros = new ObservableCollection<string>();
                return;
            }

            var allErrors = new List<string>();
            {
                foreach (var propriedade in _propriedadesDoModel)
                {
                    var errosDaPropriedade = ErrorsContainer.GetErrors(propriedade);
                    var errosEmLista = errosDaPropriedade.Select(x => x.ErrorMessage);

                    allErrors.AddRange(errosEmLista);
                }
            }

            TodosErros = new ObservableCollection<string>(allErrors);
        }



        //


        public class ValidadorConfig
        {
            public bool InicializarModelAutoValidando { get; set; }
            public TimeSpan TempoDeEsperaParaValidacao { get; set; }
            public bool DispararValidacaoAhCadaDigitoInformadoNosCampos { get; set; }

            public ValidadorConfig()
            {
                ConfiguracoesPadroes();
            }

            public ValidadorConfig(bool inicializarModelAutoValidando, bool dispararValidacaoAhCadaDigitoInformadoNosCampos,
                                   TimeSpan tempoDeEsperaParaValidacao)
            {
                InicializarModelAutoValidando = inicializarModelAutoValidando;
                DispararValidacaoAhCadaDigitoInformadoNosCampos = dispararValidacaoAhCadaDigitoInformadoNosCampos;
                TempoDeEsperaParaValidacao = tempoDeEsperaParaValidacao;
            }

            private void ConfiguracoesPadroes()
            {
                InicializarModelAutoValidando = true;
                DispararValidacaoAhCadaDigitoInformadoNosCampos = true;
                TempoDeEsperaParaValidacao = TimeSpan.FromMilliseconds(500);
            }
        }
    }
}
